//实现语音识别子服务
#pragma once
#include<brpc/server.h>
#include<butil/logging.h>
#include"data_es.hpp"
#include"mysql_user.hpp" 
#include"data_redis.hpp" 
#include"logger.hpp"  //日志模块封装
#include"etcd.hpp"   //服务注册模块封装

#include"user.hxx"
#include"user-odb.hxx"

#include"data_redis.hpp"
#include"dms.hpp"
#include"utils.hpp"
#include"channel.hpp"

#include"user.pb.h"
#include"base.pb.h"
#include"file.pb.h"



namespace wzl
{
    class UserServerImpl : public wzl::UserService
    {
    public:
        UserServerImpl(const DMSClient::ptr& dms_client,\
        const std::shared_ptr<elasticlient::Client>& es_client,\
        const std::shared_ptr<odb::core::database>&  mysql_client,\
        const std::shared_ptr<sw::redis::Redis>& redis_client,\
        const ServiceManager::ptr& channel_manager,\
        const std::string& file_service_name)
            :_es_user(std::make_shared<ESUser>(es_client))
            ,_mysql_user(std::make_shared<UserTable>(mysql_client))
            ,_redis_session(std::make_shared<Session>(redis_client))
            ,_redis_status(std::make_shared<Status>(redis_client))
            ,_redis_codes(std::make_shared<Codes>(redis_client))
            ,_file_service_name(file_service_name)
            ,_mm_channels(channel_manager)
            ,_dms_client(dms_client)
        {
            _es_user->createIndex();
        }
        ~UserServerImpl()
        {}
    // 用户注册:
    // 1.从请求中取出昵称和密码
    // 2.检查昵称是否合法(只能包含字母,数字,连字符-,下划线_,长度限制3~15之间)
    // 3.检查密码是否合法(只能包含字母,数字,长度限制6~15之间)
    // 4.根据昵称在数据库中判断昵称是否已经存在
    // 5.向数据库新增数据
    // 6.向ES服务器中新增用户信息
    // 7.组织响应,进行成功与否像响应即可
        bool nickname_check(const std::string& nickname)
        {
            return nickname.size() <22;
        }
        bool password_check(const std::string& password)
        {
            if(password.size() > 15 || password.size() < 6)
            {
                LOG_ERROR("密码长度不合法: {} -{}",password,password.size());
                return false;
            }
            for (int i = 0; i < password.size(); i++) {
                if (!((password[i] > 'a' && password[i] < 'z') ||
                    (password[i] > 'A' && password[i] < 'Z') ||
                    (password[i] > '0' && password[i] < '9') ||
                    password[i] == '_' || password[i] == '-')) {
                    LOG_ERROR("密码字符不合法：{}", password);
                    return false;
                }
            }
            return true;
        }
        void UserRegister(google::protobuf::RpcController* controller,
            const ::wzl::UserRegisterReq* request,
            ::wzl::UserRegisterRsp* response,
            ::google::protobuf::Closure* done)
        {
            LOG_DEBUG("收到用户注册请求");
            brpc::ClosureGuard rpc_guard(done);
            //定义一个错误处理函数,出错时被调用
            auto err_response = [this,response](const std::string& rid,const std::string& errmsg)->void{
                response->set_request_id(rid);
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };
            // 1.从请求中取出昵称和密码
            std::string nickname = request->nickname();
            std::string password = request->password();
            // 2.检查昵称是否合法(只能包含字母,数字,连字符-,下划线_,长度限制3~15之间)
            bool ret = nickname_check(nickname);
            if(ret = false)
            {
                LOG_ERROR("{} - 用户名长度不合法",request->request_id());
                return err_response(request->request_id(),"用户名长度不合法");
            }
            // 3.检查密码是否合法(只能包含字母,数字,长度限制6~15之间)
            ret = password_check(password);
            if(ret == false)
            {
                LOG_ERROR("{} - 密码格式不合法",request->request_id());
                return err_response(request->request_id(),"密码格式不合法");
            }
            // 4.根据昵称在数据库中判断昵称是否已经存在
            auto user = _mysql_user->select_by_nickname(nickname);
            if(user)
            {   
                LOG_ERROR("{} - 用户名被占用 - {}",request->request_id(),nickname);
                return err_response(request->request_id(),"用户名被占用");
            }
            // 5.向数据库新增数据
            std::string uid = uuid();
            user = std::make_shared<User>(uid,nickname,password);
            ret = _mysql_user->insert(user);
            if(ret == false)
            {
                LOG_ERROR("{} - MySQL数据库新增失败",request->request_id());
                return err_response(request->request_id(),"MySQL数据库新增失败");
            }
            // 6.向ES服务器中新增用户信息
            ret = _es_user->appendData(uid,"",nickname,"","");
            if(ret == false)
            {
                LOG_ERROR("{} - ES搜索引擎新增数据失败",request->request_id());
                return err_response(request->request_id(),"ES搜索引擎新增数据失败");
            }
            // 7.组织响应,进行成功与否像响应即可
            response->set_request_id(request->request_id());
            response->set_success(true);
        }

    // 用户登录:
    // 1.从请求中取出昵称和密码
    // 2.通过昵称获取用户信息,进行密码判断是否一致
    // 3.根据redis中的登陆标记信息是否存在判断用户是否已经登陆
    // 4.构造会话ID,生成会话键值对,向redis中添加会话信息以及登陆标记信息
    // 5.组织响应,返回会话生成ID
        virtual void UserLogin(::google::protobuf::RpcController* controller,
                const ::wzl::UserLoginReq* request,
                ::wzl::UserLoginRsp* response,
                ::google::protobuf::Closure* done)
        {
            LOG_DEBUG("收到用户登录请求");
            brpc::ClosureGuard rpc_guard(done);
            auto err_response = [this,response](const std::string& rid,const std::string& errmsg)->void{
                response->set_request_id(rid);
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };
            //1.从请求中取出昵称和密码
            std::string nickname = request->nickname();
            std::string password = request->password();
            //2.通过昵称获取用户信息,进行密码判断是否一致
            auto user = _mysql_user->select_by_nickname(nickname);
            if(!user || password != user->password())
            {   
                LOG_ERROR("{} - 用户名或密码错误 - {}",request->request_id(),nickname);
                return err_response(request->request_id(),"用户名或密码错误");
            }
            // 3.根据redis中的登陆标记信息是否存在判断用户是否已经登陆
            bool ret = _redis_status->exists(user->user_id());
            if(ret == true)
            {
                LOG_ERROR("{} - 用户已在其他地方登录 - {}",request->request_id(),nickname);
                return err_response(request->request_id(),"用户已在其他地方登录");
            }
            // 4.构造会话ID,生成会话键值对,向redis中添加会话信息以及登陆标记信息
            std::string ssid = uuid();
            _redis_session->append(ssid,user->user_id());
            //添加用户登陆信息
            _redis_status->append(user->user_id());
            // 5.组织响应,返回会话生成ID
            response->set_request_id(request->request_id());
            response->set_login_session_id(ssid);
            response->set_success(true);
        }
    // 获取短信验证码:
    // 1.从请求中取出手机号码
    // 2.验证手机号码格式是否正确
    // 3.生成4位随机验证码
    // 4.基于短信平台SDK发送验证码
    // 5.构造验证码ID,添加到redis验证码映射键值索引中
    // 6.组织响应,返回生成的验证码ID
        bool phone_check(const std::string& phone)
        {
            if(phone.size() != 11) {return false;}
            if(phone[0] != '1') {return false;}
            if(phone[1] < '3' || phone[1] > '9') {return false;}
            for(int i = 2; i < 11; i++)
            {
                if(phone[i] < '0' || phone[i] > '9')
                {
                    return false;
                }
            }
            return true;
        }
        virtual void GetPhoneVerifyCode(::google::protobuf::RpcController* controller,
            const ::wzl::PhoneVerifyCodeReq* request,
            ::wzl::PhoneVerifyCodeRsp* response,
            ::google::protobuf::Closure* done)
        {
            LOG_DEBUG("收到用户获取验证码请求");
            brpc::ClosureGuard rpc_guard(done);
            auto err_response = [this,response](const std::string& rid,const std::string& errmsg)->void{
                response->set_request_id(rid);
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };
            // 1.从请求中取出手机号码
            std::string phone = request->phone_number();
            // 2.验证手机号码格式是否正确
            bool ret = phone_check(phone);
            if(ret == false)
            {
                LOG_ERROR("{} - 手机号码格式错误 - {}",request->request_id(),phone);
                return err_response(request->request_id(),"手机号码格式错误");
            }
            // 3.生成4位随机验证码
            std::string code_id = uuid();
            std::string code = vcode();
            // 4.基于短信平台SDK发送验证码
            ret = _dms_client->send(phone,code);
            if(ret == false)
            {
                LOG_ERROR("{} - 短信验证码发送 - {}",request->request_id(),phone);
                return err_response(request->request_id(),"短信验证码发送");
            }
            // 5.构造验证码ID,添加到redis验证码映射键值索引中
            _redis_codes->append(code_id,code);
            
            // 6.组织响应,返回生成的验证码ID
            response->set_request_id(request->request_id());
            response->set_success(true);
            response->set_verify_code_id(code_id);
        }
    // 手机号注册
    // 1.从请求中取出手机号码和验证码
    // 2.检查注册手机号码是否合法
    // 3.从redis数据库中进行验证码ID-验证码一致性匹配
    // 4.通过数据库查询判断手机号是否已注册过
    // 5.向数据库新增用户信息
    // 6.向ES服务器中新增用户信息
    // 7.组织响应,返回注册成功与否
        virtual void PhoneRegister(::google::protobuf::RpcController* controller,
            const ::wzl::PhoneRegisterReq* request,
            ::wzl::PhoneRegisterRsp* response,
            ::google::protobuf::Closure* done)
        {
            LOG_DEBUG("收到用户手机号注册请求");
            brpc::ClosureGuard rpc_guard(done);
            auto err_response = [this,response](const std::string& rid,const std::string& errmsg)->void{
                response->set_request_id(rid);
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };
            // 1.从请求中取出手机号码和验证码
            std::string phone = request->phone_number();
            std::string code = request->verify_code();
            std::string code_id = request->verify_code_id();  
            // 2.检查注册手机号码是否合法
            bool ret = phone_check(phone);
            if(ret == false)
            {
                LOG_ERROR("{} - 手机号码格式错误 - {}",request->request_id(),phone);
                return err_response(request->request_id(),"手机号码格式错误");
            }
            // 3.从redis数据库中进行验证码ID-验证码一致性匹配
            auto vcode = _redis_codes->code(code_id);
            if(vcode != code)
            {
                LOG_ERROR("{} - 验证码错误 - {}-{}",request->request_id(),code_id,code);
                return err_response(request->request_id(),"验证码错误");
            }
            // 4.通过数据库查询判断手机号是否已注册过
            auto user = _mysql_user->select_by_phone(phone);
            if(user)
            {
                LOG_ERROR("{} - 改手机号已注册过用户 - {}",request->request_id(),phone);
                return err_response(request->request_id(),"改手机号已注册过用户");
            }
            // 5.向数据库新增用户信息
            std::string uid = uuid();
            user = std::make_shared<User>(uid,phone);
            ret = _mysql_user->insert(user);
            if(ret == false)
            {
                LOG_ERROR("{} - 向数据库添加用户信息失败 - {}",request->request_id(),phone);
                return err_response(request->request_id(),"向数据库添加用户信息失败");
            }
            // 6.向ES服务器中新增用户信息
            ret = _es_user->appendData(uid,phone,uid,"","");
            if(ret == false)
            {
                LOG_ERROR("{} - ES搜索引擎新增数据失败",request->request_id());
                return err_response(request->request_id(),"ES搜索引擎新增数据失败");
            }
            // 7.组织响应,返回注册成功与否
            response->set_request_id(request->request_id());
            response->set_success(true);
        }
        
    // 手机号登陆
    // 1.从请求中取出手机号码和验证码ID,以及验证码
    // 2.检查注册手机号码是否合法
    // 3.通过手机号数据进行用户信息查询,判断用户是否存在
    // 4.从redis数据库中进行验证码ID-验证码一致性匹配
    // 5.根据redis中的登录标记信息是否存在判断用户是否已经登陆
    // 6.构造会话ID,生成会话键值对,向redis中添加会话信息以及登录标记信息
    // 7.组织响应,返回生成的会话ID
        virtual void PhoneLogin(::google::protobuf::RpcController* controller,
            const ::wzl::PhoneLoginReq* request,
            ::wzl::PhoneLoginRsp* response,
            ::google::protobuf::Closure* done)
        {
            LOG_DEBUG("收到用户手机号登录请求");
            brpc::ClosureGuard rpc_guard(done);
            auto err_response = [this,response](const std::string& rid,const std::string& errmsg)->void{
                response->set_request_id(rid);
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };
            // 1.从请求中取出手机号码和验证码ID,以及验证码
            std::string phone = request->phone_number();
            std::string code = request->verify_code();
            std::string code_id = request->verify_code_id();
            // 2.检查注册手机号码是否合法
            bool ret = phone_check(phone);
            if(ret == false)
            {
                LOG_ERROR("{} - 手机号码格式错误 - {}",request->request_id(),phone);
                return err_response(request->request_id(),"手机号码格式错误");
            }
            // 3.3.通过手机号数据进行用户信息查询,判断用户是否存在
            auto user = _mysql_user->select_by_phone(phone);
            if(!user)
            {
                LOG_ERROR("{} - 改手机号未注册用户 - {}",request->request_id(),phone);
                return err_response(request->request_id(),"改手机号未注册用户");
            }
            // 4.从redis数据库中进行验证码ID-验证码一致性匹配
            auto vcode = _redis_codes->code(code_id);
            if(vcode != code)
            {
                LOG_ERROR("{} - 验证码错误 - {}-{}",request->request_id(),code_id,code);
                return err_response(request->request_id(),"验证码错误");
            }
            _redis_codes->remove(code_id);
            // 5.根据redis中的登录标记信息是否存在判断用户是否已经登陆
            ret = _redis_status->exists(user->user_id());
            if(ret == true)
            {
                LOG_ERROR("{} - 用户已在其他地方登录 - {}",request->request_id(),phone);
                return err_response(request->request_id(),"用户已在其他地方登录");
            }
            // 6.构造会话ID,生成会话键值对,向redis中添加会话信息以及登录标记信息
            std::string ssid = uuid();
            _redis_session->append(ssid,user->user_id());
            //添加用户登录信息
            _redis_status->append(user->user_id());
            // 7.组织响应,返回生成的会话ID
            response->set_request_id(request->request_id());
            response->set_login_session_id(ssid);
            response->set_success(true);
        }
        //从这一步开始是用户登陆之后才会进行的操作
    // 获取用户信息:
    // 1.从请求中取出用户ID
    // 2.通过用户ID,从数据库中查询用户信息
    // 3.根据用户信息中的头像ID,从文件服务器获取头像文件数据,组织完整用户信息
    // 4.组织响应,返回用户信息
        virtual void GetUserInfo(::google::protobuf::RpcController* controller,
            const ::wzl::GetUserInfoReq* request,
            ::wzl::GetUserInfoRsp* response,
            ::google::protobuf::Closure* done)
        {
            LOG_DEBUG("收到获取用户信息请求");
            brpc::ClosureGuard rpc_guard(done);
            auto err_response = [this,response](const std::string& rid,const std::string& errmsg)->void{
                response->set_request_id(rid);
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };
            // 1.从请求中取出用户ID
            std::string uid = request->user_id();
            // 2.通过用户ID,从数据库中查询用户信息
            auto user = _mysql_user->select_by_id(uid);
            if(!user)
            {
                LOG_ERROR("{} - 未找到用户信息 - {}",request->request_id(),uid);
                return err_response(request->request_id(),"未找到用户信息");
            }
            // 3.根据用户信息中的头像ID,从文件服务器获取头像文件数据,组织完整用户信息
            UserInfo* user_info = response->mutable_user_info();
            user_info->set_user_id(user->user_id());
            user_info->set_nickname(user->nickname());
            user_info->set_description(user->description());
            user_info->set_phone(user->phone());

            if(!user->avatar_id().empty())
            {
                //从信道管理对象中,获取链接到了文件管理服务的channel
                auto channel = _mm_channels->choose(_file_service_name);
                if(!channel)
                {
                    LOG_ERROR("{} - 未找到文件管理子服务节点 - {} - {}",request->request_id(),_file_service_name,uid);
                    return err_response(request->request_id(),"未找到文件管理子服务节点");
                }
                //进行文件子服务的rpc请求,进行头像文件下载
                wzl::FileService_Stub stub(channel.get());
                wzl::GetSingleFileReq req;
                wzl::GetSingleFileRsp rsp;
                req.set_request_id(request->request_id());
                req.set_file_id(user->avatar_id());
                brpc::Controller cntl;
                stub.GetSingleFile(&cntl,&req,&rsp,nullptr);

                if(cntl.Failed() == true || rsp.success() == false)
                {
                    LOG_ERROR("{} - 文件子服务调用失败 - {}",request->request_id(),uid);
                    return err_response(request->request_id(),"文件子服务调用失败");
                }
                user_info->set_avatar(rsp.file_data().file_content());
            }
            // 4.组织响应,返回用户信息
            response->set_request_id(request->request_id());
            response->set_success(true);
        }
        virtual void GetMultiUserInfo(::google::protobuf::RpcController* controller,
            const ::wzl::GetMultiUserInfoReq* request,
            ::wzl::GetMultiUserInfoRsp* response,
            ::google::protobuf::Closure* done)
        {
            LOG_DEBUG("收到获取批量用户信息请求");
            brpc::ClosureGuard rpc_guard(done);
             //1.定义错误回调
            auto err_response = [this,response](const std::string& rid,const std::string& errmsg)->void{
                response->set_request_id(rid);
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;
            };
            //2.从请求中取出用户ID ---列表
            std::vector<std::string> uid_lists;
            for(int i = 0 ;i < request->users_id_size();i++)
            {
                uid_lists.push_back(request->users_id(i));
            } 
            //3.从数据库进行批量用户信息查询
            auto users = _mysql_user->select_multi_users(uid_lists);
            if(users.size() != request->users_id_size())
            {
                LOG_ERROR("{}- 从数据库查找的用户信息数量不一致:{} - {}",request->request_id(),request->users_id_size(),users.size());
                return err_response(request->request_id(),"从数据库查找的用户信息数量不一致");
            }
            //4.批量从文件管理子服务进行文件下载
            auto channel = _mm_channels->choose(_file_service_name);
            if(!channel)
            {
                LOG_ERROR("{} - 未找到文件管理子服务节点 - {}",request->request_id(),_file_service_name);
                return err_response(request->request_id(),"未找到文件管理子服务节点");
            }
            wzl::FileService_Stub stub(channel.get());
            wzl::GetMultiFileReq req;
            wzl::GetMultiFileRsp rsp;
            req.set_request_id(request->request_id());
            for(auto& user :users)
            {
                if (user.avatar_id().empty()) continue;
                req.add_file_id_list(user.avatar_id());
            }
            brpc::Controller cntl;
            stub.GetMultiFile(&cntl,&req,&rsp,nullptr);
            if(cntl.Failed() == true || rsp.success() == false)
            {
                LOG_ERROR("{}- 文件子服务调用失败: {} - {}",request->request_id(),_file_service_name,cntl.ErrorText());
                return err_response(request->request_id(),"文件子服务调用失败");
            }
            //5.组织响应
            for(auto& user : users)
            {
                auto user_map = response->mutable_users_info();   ///本次请求要响应的用户信息map
                auto file_map = rsp.mutable_file_data();     //这是批量文件响应请求中的map
                UserInfo user_info;
                user_info.set_user_id(user.user_id());
                user_info.set_nickname(user.nickname());
                user_info.set_description(user.description());
                user_info.set_phone(user.phone());
                user_info.set_avatar((*file_map)[user.avatar_id()].file_content());
                (*user_map)[user_info.user_id()] = user_info;

                response->set_request_id(request->request_id());
                response->set_success(true);
            }
        }
    // 设置头像
    // 1.从请求中取出用户ID与头像数据
    // 2.从数据库中通过用户ID进行用户查询,判断用户是否存在
    // 3.上传用户头像到文件子服务
    // 4.将返回的头像文件ID更新到数据库中
    // 5.更新ES服务器中的用户信息
    // 6.组织响应,返回更新成功与否
        virtual void SetUserAvatar(::google::protobuf::RpcController* controller,
            const ::wzl::SetUserAvatarReq* request,
            ::wzl::SetUserAvatarRsp* response,
            ::google::protobuf::Closure* done)
        {
            LOG_DEBUG("收到用户设置头像请求");
            brpc::ClosureGuard rpc_guard(done);
            auto err_response = [this,response](const std::string& rid,const std::string& errmsg)->void{
                response->set_request_id(rid);
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;};
        
            // 1.从请求中取出用户ID与头像数据
            std::string uid = request->user_id();
            // 2.从数据库中通过用户ID进行用户查询,判断用户是否存在
            auto user = _mysql_user->select_by_id(uid);
            if(!user)
            {
                LOG_ERROR("{} - 未找到用户信息 - {}",request->request_id(),uid);
                return err_response(request->request_id(),"未找到用户信息");
            }
            // 3.上传用户头像到文件子服务
            auto channel = _mm_channels->choose(_file_service_name);
            if(!channel)
            {
                LOG_ERROR("{} - 未找到文件管理子服务节点 - {}",request->request_id(),_file_service_name);
                return err_response(request->request_id(),"未找到文件管理子服务节点");
            }
            wzl::FileService_Stub stub(channel.get());
            wzl::PutSingleFileReq req;
            wzl::PutSingleFileRsp rsp;
            req.set_request_id(request->request_id());
            req.mutable_file_data()->set_file_name("");
            req.mutable_file_data()->set_file_size(request->avatar().size());
            req.mutable_file_data()->set_file_content(request->avatar());
            brpc::Controller cntl;
            stub.PutSingleFile(&cntl,&req,&rsp,nullptr);
            if(cntl.Failed() == true || rsp.success() == false)
            {
                LOG_ERROR("{}- 文件子服务调用失败: {}",request->request_id(),cntl.ErrorText());
                return err_response(request->request_id(),"文件子服务调用失败");
            }
            std::string avatar_id = rsp.file_info().file_id();
            // 4.将返回的头像文件ID更新到数据库中
            user->avatar_id(avatar_id);
            bool ret = _mysql_user->update(user);
            if(ret == false)
            {
                LOG_ERROR("{} - 更新数据库头像ID失败 - {}",request->request_id(),avatar_id);
                return err_response(request->request_id(),"更新数据库头像ID失败");
            }
            // 5.更新ES服务器中的用户信息
            ret = _es_user->appendData(user->user_id(),user->phone(),user->nickname(),user->description(),user->avatar_id());
            if(ret == false)
            {
                LOG_ERROR("{} - 更新搜索引擎用户头像ID失败 - {}",request->request_id(),avatar_id);
                return err_response(request->request_id(),"更新搜索引擎用户头像ID失败");
            }
            // 6.组织响应,返回更新成功与否
            response->set_request_id(request->request_id());
            response->set_success(true);
        }
    // 设置昵称
    // 1.从请求中取出用户ID与新昵称
    // 2.判断昵称格式是否正确
    // 3.从数据库通过用户ID进行用户信息查询,判断用户是否存在
    // 4.将新的昵称更新到数据库中
    // 5.更新ES服务器中的用户信息
    // 6.组织响应,返回更新成功与否
        virtual void SetUserNickname(::google::protobuf::RpcController* controller,
            const ::wzl::SetUserNicknameReq* request,
            ::wzl::SetUserNicknameRsp* response,
            ::google::protobuf::Closure* done)
        {
            LOG_DEBUG("收到用户设置昵称请求");
            brpc::ClosureGuard rpc_guard(done);
            auto err_response = [this,response](const std::string& rid,const std::string& errmsg)->void{
                response->set_request_id(rid);
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;};
            // 1.从请求中取出用户ID与新昵称
            std::string uid = request->user_id();
            std::string new_nickname = request->nickname();
            // 2.判断昵称格式是否正确
            bool ret = nickname_check(new_nickname);
            if(ret == false)
            {
                LOG_ERROR("{} - 用户名长度不合法",request->request_id());
                return err_response(request->request_id(),"用户名长度不合法");
            }
            // 3.从数据库通过用户ID进行用户信息查询,判断用户是否存在
            auto user = _mysql_user->select_by_id(uid);
            if(!user)
            {   
                LOG_ERROR("{} - 用户名被占用 - {}",request->request_id(),uid);
                return err_response(request->request_id(),"用户名被占用");
            }
            // 4.将新的昵称更新到数据库中
            user->nickname(new_nickname);
            ret = _mysql_user->update(user);
            if(ret == false)
            {
                LOG_ERROR("{} - 更新数据库用户昵称失败 - {}",request->request_id(),new_nickname);
                return err_response(request->request_id(),"更新数据库用户昵称失败");
            }
            // 5.更新ES服务器中的用户信息
            ret = _es_user->appendData(user->user_id(),user->phone(),user->nickname(),user->description(),user->avatar_id());
            if(ret == false)
            {
                LOG_ERROR("{} - 更新搜索引擎用户昵称失败 - {}",request->request_id(),new_nickname);
                return err_response(request->request_id(),"更新搜索引擎用户昵称失败");
            }
            // 6.组织响应,返回更新成功与否
            response->set_request_id(request->request_id());
            response->set_success(true);
        }
        
    // 设置签名
    // 1.从请求中取出用户ID与新签名
    // 2.从数据库通过用户ID进行用户信息查询,判断用户是否存在
    // 3.将新的签名更新到数据库中
    // 4.更新ES服务器中的用户信息
    // 5.组织响应,返回更新成功与否
        virtual void SetUserDescription(::google::protobuf::RpcController* controller,
            const ::wzl::SetUserDescriptionReq* request,
            ::wzl::SetUserDescriptionRsp* response,
            ::google::protobuf::Closure* done)
        {
            LOG_DEBUG("收到用户设置签名请求");
            brpc::ClosureGuard rpc_guard(done);
            auto err_response = [this,response](const std::string& rid,const std::string& errmsg)->void{
                response->set_request_id(rid);
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;};
            // 1.从请求中取出用户ID
            std::string uid = request->user_id();
            std::string new_description = request->description();
            // 2.从数据库通过用户ID进行用户信息查询,判断用户是否存在
            auto user = _mysql_user->select_by_id(uid);
            if(!user)
            {   
                LOG_ERROR("{} - 用户名被占用 - {}",request->request_id(),uid);
                return err_response(request->request_id(),"用户名被占用");
            }
            // 3.将新的签名更新到数据库中
            user->description(new_description);
            bool ret = _mysql_user->update(user);
            if(ret == false)
            {
                LOG_ERROR("{} - 更新数据库用户签名失败 - {}",request->request_id(),new_description);
                return err_response(request->request_id(),"更新数据库用户签名失败");
            }
            // 4.更新ES服务器中的用户信息
            ret = _es_user->appendData(user->user_id(),user->phone(),user->nickname(),user->description(),user->avatar_id());
            if(ret == false)
            {
                LOG_ERROR("{} - 更新搜索引擎用户签名失败 - {}",request->request_id(),new_description);
                return err_response(request->request_id(),"更新搜索引擎用户签名失败");
            }
            // 5.组织响应,返回更新成功与否
            response->set_request_id(request->request_id());
            response->set_success(true);
        }
    // 设置手机号
    // 1.从请求中取出用户ID,新手机号,验证码和验证码ID
    // 2.对验证码进行验证
    // 3.从数据库通过用户ID进行用户信息查询,判断用户是否存在
    // 4.将新的手机号更新到数据库中
    // 5.更新ES服务器中的用户信息
    // 6.组织响应,返回更新成功与否
        virtual void SetUserPhoneNumber(::google::protobuf::RpcController* controller,
            const ::wzl::SetUserPhoneNumberReq* request,
            ::wzl::SetUserPhoneNumberRsp* response,
            ::google::protobuf::Closure* done)
        {
            LOG_DEBUG("收到用户设置手机号请求");
            brpc::ClosureGuard rpc_guard(done);
            auto err_response = [this,response](const std::string& rid,const std::string& errmsg)->void{
                response->set_request_id(rid);
                response->set_success(false);
                response->set_errmsg(errmsg);
                return;};
            // 1.从请求中取出用户ID,新手机号,验证码和验证码ID
            std::string uid = request->user_id();
            std::string new_phone = request->phone_number();
            std::string code = request->phone_verify_code();
            std::string code_id = request->phone_verify_code_id();
            // 2.对验证码进行验证
            auto vcode = _redis_codes->code(code_id);
            if(vcode != code)
            {
                LOG_ERROR("{} - 验证码错误 - {}-{}",request->request_id(),code_id,code);
                return err_response(request->request_id(),"验证码错误");
            }
            // 3.从数据库通过用户ID进行用户信息查询,判断用户是否存在
            auto user = _mysql_user->select_by_id(uid);
            if(!user)
            {   
                LOG_ERROR("{} - 用户名被占用 - {}",request->request_id(),uid);
                return err_response(request->request_id(),"用户名被占用");
            }
            // 4.将新的手机号更新到数据库中
            user->nickname(new_phone);
            bool ret = _mysql_user->update(user);
            if(ret == false)
            {
                LOG_ERROR("{} - 更新数据库用户手机号失败 - {}",request->request_id(),new_phone);
                return err_response(request->request_id(),"更新数据库用户手机号失败");
            }
            // 5.更新ES服务器中的用户信息
            ret = _es_user->appendData(user->user_id(),user->phone(),user->nickname(),user->description(),user->avatar_id());
            if(ret == false)
            {
                LOG_ERROR("{} - 更新搜索引擎用户手机号失败 - {}",request->request_id(),new_phone);
                return err_response(request->request_id(),"更新搜索引擎用户手机号失败");
            }
            // 6.组织响应,返回更新成功与否
            response->set_request_id(request->request_id());
            response->set_success(true);
        }
    private:
        ESUser::ptr _es_user;
        UserTable::ptr _mysql_user; 
        Session::ptr _redis_session;
        Status::ptr _redis_status;
        Codes::ptr _redis_codes;
        //rpc客户端相关对象
        std::string _file_service_name;
        ServiceManager::ptr _mm_channels;
        Discovery::ptr _service_discoverer;
        DMSClient::ptr _dms_client;
    };

    class UserServer
    {
    public:
        UserServer(const Discovery::ptr& service_discoverer, \
            const Registry::ptr& registry_client,\
            const std::shared_ptr<elasticlient::Client>& es_client,\
            const std::shared_ptr<odb::core::database>&  mysql_client,\
            const std::shared_ptr<sw::redis::Redis>& redis_client,\
            const std::shared_ptr<brpc::Server>& server)
            :_service_discoverer(service_discoverer)
            ,_registry_client(registry_client)
            ,_es_client(es_client)
            ,_mysql_client(mysql_client)
            ,_redis_client(redis_client)
            ,_rpc_server(server)
        {}
        using ptr = std::shared_ptr<UserServer>;
        ~UserServer()
        {}
        //搭建RPC服务器,并启动服务器
        void start()
        {
            _rpc_server->RunUntilAskedToQuit();
        }
    private:
        Discovery::ptr _service_discoverer;
        Registry::ptr _registry_client;
        std::shared_ptr<brpc::Server> _rpc_server;

        std::shared_ptr<elasticlient::Client> _es_client;
        std::shared_ptr<odb::core::database>  _mysql_client;
        std::shared_ptr<sw::redis::Redis> _redis_client;
    };

    class UserServerBuilder
    {
    public:
    //构造dms客户端对象
        void make_dms_object(const std::string& access_key_id,const std::string& access_key_secret)
        {
            _dms_client = std::make_shared<DMSClient> (access_key_id,access_key_secret);
        }
        //构造es客户端对象
        void make_es_object(const std::vector<std::string> host_list)
        {
            _es_client = ESClientFactory::create(host_list);
        }
        //构造mysql客户端对象
        void make_mysql_object (const std::string& user,\
        const std::string& pswd,\
        const std::string& host,\
        const std::string& db,\
        const std::string& cset,int port,\
        int conn_poll_count)
        {
            _mysql_client = ODBFactory::create(user,pswd,host,db,cset,port,conn_poll_count);
        }
        //构造redis客户端对象
        void make_redis_object(const std::string& host,int32_t port,int32_t db,bool keepalive)
        {
            _redis_client = RedisClientFactory::create(host,port,db,keepalive);
        }
        //构造服务发现客户端对象 & 信道管理客户端对象
        void make_discovery_object(const std::string& reg_host,\
            const std::string& base_service_name,\
            const std::string& file_service_name)
        {
            _file_service_name = file_service_name;
            _mm_channels = std::make_shared<ServiceManager>();
            _mm_channels->declared(file_service_name);
            LOG_DEBUG("设置文件子服务为需要添加管理的子服务: {}",file_service_name);
            auto put_cb = std::bind(&ServiceManager::onServiceOnline,_mm_channels.get()\
            ,std::placeholders::_1,std::placeholders::_2);
            auto del_cb = std::bind(&ServiceManager::onServiceOffline,_mm_channels.get()\
            ,std::placeholders::_1,std::placeholders::_2);
            _service_discoverer = std::make_shared<Discovery> (reg_host,base_service_name,put_cb,del_cb);
            

        }
        //用于构造注册服务客户端对象
        void make_reg_object(const std::string& reg_host,
            const std::string& service_name,
            const std::string &access_host)
        {
            _registry_client = std::make_shared<Registry>(reg_host);
            _registry_client->registry(service_name,access_host);
        }
        //搭建RPC服务器,并启动服务器
        void make_rpc_server(uint64_t port,int32_t timeout,uint8_t num_threads)
        {
            if(!_es_client)
            {
                LOG_ERROR("还未初始化搜索引擎模块");
                abort();
            }
            if(!_mysql_client)
            {
                LOG_ERROR("还未初始化mysql数据库模块");
                abort();
            }
            if(!_redis_client)
            {
                LOG_ERROR("还未初始化redis数据库模块");
                abort();
            }
            if(!_mm_channels)
            {
                LOG_ERROR("还未初始化信道管理模块");
                abort();
            }
            if(!_dms_client)
            {
                LOG_ERROR("还未初始化dms模块");
                abort();
            }
            _rpc_server = std::make_shared<brpc::Server>();
            UserServerImpl* user_service = new UserServerImpl(_dms_client,_es_client,_mysql_client,_redis_client,\
                _mm_channels,_file_service_name);
                
            int ret = _rpc_server->AddService(user_service,brpc::ServiceOwnership::SERVER_OWNS_SERVICE);
            if(ret == -1)
            {
                LOG_ERROR("添加rpc服务失败");
                abort();
            }
            brpc::ServerOptions options;
            options.idle_timeout_sec = timeout; //链接空闲时间-超时后连接被关闭
            options.num_threads = num_threads; //io线程数量
            ret = _rpc_server->Start(port,&options);
            if(ret == -1)
            {
                LOG_ERROR("服务器启动失败");
                abort();
            }
        }

        UserServer::ptr build()
        {
            
            if(!_service_discoverer)
            {
                LOG_ERROR("还未初始化服务发现模块");
                abort();
            }
            if(!_registry_client)
            {
                LOG_ERROR("还未初始化registry数据库模块");
                abort();
            }
            if(!_rpc_server)
            {
                LOG_ERROR("还未初始化registry数据库模块");
                abort();
            }

            UserServer::ptr server = std::make_shared<UserServer>(_service_discoverer,\
                _registry_client,_es_client, _mysql_client,_redis_client,_rpc_server);

            return server;
        }
    private:
        Registry::ptr _registry_client;
        
        std::shared_ptr<elasticlient::Client> _es_client;
        std::shared_ptr<odb::core::database>  _mysql_client;
        std::shared_ptr<sw::redis::Redis> _redis_client;

        std::string _file_service_name;
        ServiceManager::ptr _mm_channels;
        Discovery::ptr _service_discoverer;

        std::shared_ptr<DMSClient> _dms_client;

        std::shared_ptr<brpc::Server> _rpc_server;
    };
};